using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using ProxyServer.Pool;

namespace ProxyServer.ProxyImpl
{
    public abstract class Listener : IDisposable
    {
        private ILogger logger = Logger.GetLogger();

        private readonly SocketAsyncEventArgs asyncEventForAccept;

        protected Listener()
        {
            Clients = new List<Client>();
            asyncEventForAccept = BufferContext.GetSocketAsyncEventPool().Pop(false);
            asyncEventForAccept.Completed += AsyncEventForAccept_OnCompleted;
        }

        private void AsyncEventForAccept_OnCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                OnAccept(e.AcceptSocket);
            }
            else
            {
                Stop();
            }
        }

        protected int Port { get; private set; }

        protected IPAddress Address { get; private set; }

        protected Socket ListenSocket { get; private set; }

        protected List<Client> Clients { get; private set; }

        public bool IsDisposed { get; private set; }

        public bool Listening
        {
            get { return ListenSocket != null; }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }

            IsDisposed = true;

            Clients.ForEach(client => client.Dispose());

            Stop();

            asyncEventForAccept.Completed -= AsyncEventForAccept_OnCompleted;
            BufferContext.GetSocketAsyncEventPool().Push(asyncEventForAccept);
        }

        #endregion

        public void Stop()
        {
            try
            {
                ListenSocket.Close();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
            }

            Dispose();
        }

        public void Start()
        {
            try
            {
                var proxyConfig = ProxyConfigManager.ReadSetting();
                Address = IPAddress.Parse(proxyConfig.Host);
                Port = proxyConfig.Port;
                ListenSocket = new Socket(Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                ListenSocket.Bind(new IPEndPoint(Address, Port));
                ListenSocket.Listen(50);
                AcceptAsync();
            }
            catch (Exception e)
            {
                logger.Error("error start listening:" + e.Message);
                Dispose();
            }
        }

        protected void AcceptAsync()
        {
            try
            {
                asyncEventForAccept.AcceptSocket = null;
                if (!ListenSocket.AcceptAsync(asyncEventForAccept))
                {
                    OnAccept(asyncEventForAccept.AcceptSocket);
                }
            }
            catch (Exception e)
            {
                logger.Error("Accept a socket failed:" + e.Message);
                Dispose();
            }
        }

        protected void AddClient(Client client)
        {
            if (Clients.IndexOf(client) == -1)
                Clients.Add(client);
        }

        protected void RemoveClient(Client client)
        {
            Clients.Remove(client);
        }

        protected virtual void OnAccept(Socket acceptedSocket)
        {
            AcceptAsync();
        }
    }
}